package com.ruoyi.protocol;

import cn.hutool.core.util.HexUtil;
import com.ruoyi.common.constant.MessageId;
import com.ruoyi.common.core.domain.entity.socket.bean.AuthRespMsg;
import com.ruoyi.common.core.domain.entity.socket.bean.CommonRespMsg;
import com.ruoyi.common.core.domain.entity.socket.bean.Message;
import com.ruoyi.common.core.domain.entity.socket.bean.RegisterRespMsg;
import com.ruoyi.common.utils.MyByteUtil;
import com.ruoyi.handler.socket.SocketMessageHandler1;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 该编解码器必须和 LengthFieldBasedFrameDecoder 一起使用，确保接到的 ByteBuf 消息是完整的。
 */
@Component
@Slf4j
@ChannelHandler.Sharable
public class MessageCodec1 extends MessageToMessageCodec<ByteBuf, Message> {

    /**
     * 消息流水号。
     */
    private static final AtomicInteger MessageSeq = new AtomicInteger(0);


    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, List<Object> outList) throws Exception {
        ByteBuf out = ctx.alloc().buffer();
        //1,写入报文起始符 1个字节
        out.writeBytes(HexUtil.decodeHex("7e"));

        byte isNeedTransfer = 0; //是否需要转义，默认值为0，即无需转义。
        ByteBuf encodedBuf = null;
        if (msg instanceof RegisterRespMsg) {
            RegisterRespMsg registerRespMsg = (RegisterRespMsg) msg;
            encodedBuf = SocketMessageHandler1.encodeRegisterMsg(registerRespMsg);
        } else if (msg instanceof AuthRespMsg) {
            AuthRespMsg authRespMsg = (AuthRespMsg) msg;
            encodedBuf = SocketMessageHandler1.encodeAuthRespMsg(authRespMsg);
        } else if (msg instanceof CommonRespMsg) {
            CommonRespMsg commonRespMsg = new CommonRespMsg();
            encodedBuf = SocketMessageHandler1.encodeCommonRespMsg(commonRespMsg);
        }

        if (Objects.nonNull(encodedBuf)) {
            //计算并填充校验码
            byte checkSum = calcCheckSum(encodedBuf);
            encodedBuf.writeByte(checkSum);

            //检查是否需要转义
            boolean isNeed = checkIfNeedTransfer(encodedBuf);
            if (isNeed) {
                encodedBuf = transfer(encodedBuf);
                isNeedTransfer = 1;
            }

            //写入是否需要转义的标识符 (注意：此字节不要写入，灿哥不读此字节，其对所有的报文都会进行转义，不管是否需要转义)
            // out.writeByte(isNeedTransfer);

            //写入消息头，消息体，校验码
            out.writeBytes(encodedBuf);

            //写入报文结束符 1个字节
            out.writeBytes(HexUtil.decodeHex("7e"));

            log.info("<========打印编码后欲发送的字节码===========>");
            MyByteUtil.logByteBuf(out);

            outList.add(out);
        }
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> outList) throws Exception {
        Channel channel = ctx.channel();

//        log.info("<========打印接收到的字节码(未转义)===========>");
//        MyByteUtil.logByteBuf(in);

        in.markReaderIndex();

        //先检查当前报文段的可读字节数
        int readableBytes = in.readableBytes();
        if (readableBytes < 12) { //一个有效报文的最短长度为12，即：1字节的转义判断符+消息头长度最短为10个字节+消息体最短长度为0+1个字节长度的校验码=12
            log.warn("当前报文段可读字节数少于12个，报文无效，直接丢弃！");
            return;
        }

        //读取转义判断符 1个字节。转义判断符用于平台接收数据时判断是否需要进行转义处理， 占用一个字节， 只有两个值，0表示不需要转义，1表示需要转义。
        byte isTransferred = in.readByte();
        ByteBuf processedBuf;
        if (isTransferred == 1) { //需先转义还原
            ByteBuf transferedBuf = unTransfer(in);
            processedBuf = transferedBuf;
            processedBuf.readByte(); //注意：因为processedBuf是一个新的ByteBuf对象，需再次读一下转义判断符，将读指针往右移动一位
        } else { //无需转义
            processedBuf = in;
        }

        log.info("<========打印接收到的字节码(已转义)===========>");
        MyByteUtil.logByteBuf(processedBuf);

        //读取校验码
        byte checksum = processedBuf.getByte(processedBuf.writerIndex() - 1); //processedBuf的最后一个字节即为校验码。

        //进行校验码校验
        boolean checkResult = verifyChecksum(processedBuf, checksum);
        if (!checkResult) {
            log.warn("校验失败！");
        } else {
            log.info("校验成功！");
//            parseData(channel, processedBuf);
        }

        parseData(channel, processedBuf);
    }

    private void parseData(Channel channel, ByteBuf processedBuf) {
        //1，读消息头。10或14个字节，无分包则为10字节，有分包则为14个字节，目前基本都无分包情况。
        //1.1 读取消息ID，2个字节。
        int messageId = processedBuf.readUnsignedShort();
        //1.2 读取消息体属性，2个字节。
        int messageProp = processedBuf.readUnsignedShort();
        //1.2.1 读取是否分包的标识符。为1时表示消息体为长消息，进行分包发送处理，具体分包信息由消息包封装项决定；若为0，则消息头中无消息包封装项字段。
        int subpackageFlag = MyByteUtil.getBitValueFromInt(messageProp, 14);
        //1.2.2 读取消息体长度
        int messageLength = MyByteUtil.getBitValueFromInt(messageProp, 0, 10);
        //1.3 读取设备ID，4个字节。设备注册时由平台生成并下发；未注册时默认为：0x00000000
        long meterId = processedBuf.readUnsignedInt();
        //1.3 读取消息流水号，2个字节。按发送顺序从0开始循环累加；设备应答中的流水号要与平台下发指令中的流水号一一对应；平台应答中的流水号要与设备上传指令中的流水号一一对应；
        int messageSeq = processedBuf.readUnsignedShort();
        if (subpackageFlag == 1) {
            //1.4 读取消息包封装项，4个字节。
            processedBuf.readBytes(4);
        }

        log.info("messageId: {},subpackageFlag: {},messageLength: {},meterId: {},messageSeq: {}", messageId, subpackageFlag, messageLength, meterId, messageSeq);

        if (messageId == MessageId.Req_Register_MsgId) { //设备注册消息
            SocketMessageHandler1.parseRegisterMsg(channel, processedBuf, messageLength, messageSeq);
        } else if (messageId == MessageId.Req_Auth_MsgId) { //设备鉴权消息
            SocketMessageHandler1.parseAuthMsg(channel, processedBuf, messageLength);
        } else if (messageId == MessageId.Req_Heartbeat_Upload_MsgId) { //船机心跳信息上传消息
            SocketMessageHandler1.parseHeartbeatUploadMsg(channel, processedBuf, meterId, messageId);
        } else if (messageId == MessageId.Req_Heartbeat_Reupload_MsgId) { //船机心跳盲区数据补传消息
            SocketMessageHandler1.parseHeartbeatReuploadMsg(channel, processedBuf, meterId, messageId);
        } else {
            //给设备发送平台通用应答消息。
            SocketMessageHandler1.sendCommonResp(channel, messageId);
        }

//        if (messageId == MessageId.Req_Heartbeat_Upload_MsgId) { //设备心跳上传
//            SocketMessageHandler1.parseHeartbeatUploadMsg(channel, processedBuf, messageLength, meterId, messageId);
//        } else {
//            //给设备发送平台通用应答消息。
//            SocketMessageHandler1.sendCommonResp(channel, messageId);
//        }

        //3，读校验码，1个字节。
        processedBuf.readByte();
    }

    /**
     * 检查是否需要转义。若校验码、消息头以及消息体中出现 0x7e 或 0x7d，则要进行转义处理。
     *
     * @param inBuf
     * @return
     */
    private boolean checkIfNeedTransfer(ByteBuf inBuf) {
        byte currByte;
        for (int i = 0; i < inBuf.writerIndex(); i++) {
            currByte = inBuf.getByte(i);
            if (currByte == 125 || currByte == 126) { //找到0x7e或0x7d，因为需要进行转义
                return true;
            }
        }

        return false;
    }

    /**
     * 转义。
     *
     * @param inBuf
     * @return
     */
    public ByteBuf transfer(ByteBuf inBuf) {
        ByteBuf outBuf = ByteBufAllocator.DEFAULT.buffer();

        for (int i = 0; i < inBuf.writerIndex(); i++) {
            byte currByte = inBuf.getByte(i);
            outBuf.writeByte(currByte);
            if (currByte == 125) { //找到字符0x7d，需要将0x7d转成0x7d 0x01
                outBuf.writeByte(1);
            } else if (currByte == 126) { //找到字符0x7e，需要将0x7e转成0x7d 0x02
                outBuf.writeByte(2);
            }
        }

        return outBuf;
    }

    /**
     * 转义还原。
     *
     * @param inBuf
     */
    public ByteBuf unTransfer(ByteBuf inBuf) {
        ByteBuf outBuf = ByteBufAllocator.DEFAULT.buffer();

        for (int i = 0; i < inBuf.writerIndex(); i++) {
            byte currByte = inBuf.getByte(i);
            if (currByte == 125) { //找到转义字符0x7d
                log.info("转义字符0x7d所在位置索引：{}", i);
                //再读0x7d的下一个字节
                byte nextByte = inBuf.getByte(++i);
                if (nextByte == 1) { //0x01
                    outBuf.writeBytes(HexUtil.decodeHex("7d"));
                } else if (nextByte == 2) { //0x01
                    outBuf.writeBytes(HexUtil.decodeHex("7e"));
                }
            } else {
                outBuf.writeByte(currByte);
            }
        }

        return outBuf;
    }

    /**
     * 计算校验码。
     *
     * @param mergeBuf
     * @return
     */
    private byte calcCheckSum(ByteBuf mergeBuf) {
        log.info("calcCheckSum 打印计算校验码的ByteBuf:");
//        MyByteUtil.logByteBuf(mergeBuf);
        byte checksum = mergeBuf.getByte(0);
        for (int i = 1; i < mergeBuf.writerIndex(); i++) {
            checksum ^= mergeBuf.getByte(i);
        }
        log.info("calcCheckSum checksum: {}", checksum);
        return checksum;
    }

    private boolean verifyChecksum(ByteBuf in, byte checksum) {
        byte result = in.getByte(0);
        for (int i = 1; i < in.writerIndex() - 1; i++) { //注意：此处需减1，因为校验位不参与计算
            result ^= in.getByte(i);
        }
        log.info("checksum: {}, result: {}", checksum, result);

        return checksum == result;
    }
}
